package com.basker.pisces.domain.entity.data;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import com.basker.pisces.core.exception.StaleObjectException;
import com.basker.pisces.core.meta.IDataObjectMeta;
import com.basker.pisces.core.meta.bean.BeanFieldDescriptor;
import com.basker.pisces.core.repository.IDataRepository;
import com.basker.pisces.domain.annotation.ListOutput;
import com.basker.pisces.domain.annotation.PropertyOutput;
import com.basker.pisces.domain.annotation.PropertyOutput.ValueType;
import com.basker.pisces.domain.entity.data.DataEntityInputBeanDescriptor.PropertyInputDescriptor;
import com.basker.pisces.domain.entity.event.DataEntityEventPublisher;
import com.basker.pisces.domain.entity.meta.AbstractEntity;
import com.basker.pisces.domain.entity.meta.ChildEntity;
import com.basker.pisces.domain.entity.meta.Entity;
import com.basker.pisces.domain.property.InvalidPropertyValueException;
import com.basker.pisces.domain.property.meta.Property;
import com.basker.pisces.domain.property.meta.ReferenceProperty;
import com.basker.pisces.domain.value.IValueObject;
import com.basker.pisces.domain.value.ReferenceObjectValue;
import com.basker.pisces.resource.StringResources;
import com.basker.pisces.utils.ListsUtils;
import com.basker.pisces.utils.ReflectionUtils;
import com.google.common.collect.Maps;

/**
 * 数据实体抽象类，对大量接口方法做了基本实现
 *
 * @author hangwen
 */
public abstract class AbstractDataEntity implements IDataEntity {

    protected final Logger logger = LoggerFactory.getLogger(this.getClass());
    protected final DataEntityEventPublisher eventPublisher = new DataEntityEventPublisher(this);
    private Entity entity;
    private Object dataObject;

    @Override
    public void addChildObjectList(ChildEntity childEntity, List<Object> childObjectList) {
        this.assertDataObjectNotNull();
        this.addChildObjectList(childEntity, this.dataObject, childObjectList);
    }

    @Override
    public Object addNewChildObject(final String childEntityName) {
        ChildEntity childEntity = this.entity.getChildEntity(childEntityName);
        Object childObject = this.createChildObject(childEntity);

        this.addChildObjectList(childEntity, this.dataObject, Collections.singletonList(childObject));

        return childObject;
    }

    @Override
    public List<Object> batchInput(List<?> inputObjects) {
        InputOption option = new InputOption();
        return this.batchInput(inputObjects, option);
    }

    @Override
    public List<Object> batchInput(List<?> inputObjects, InputOption option) {
        Assert.notEmpty(inputObjects, "parameter 'inputObjects' is required");
        Assert.notNull(option, "parameter 'option' is required");

        List<Object> dataObjects = new ArrayList<>(inputObjects.size());
        List<Object> retObjects = dataObjects;

        // 先记录之前的数据对象
        Object previousDataObject = this.getDataObject();

        try {
            Class<?> inputType = inputObjects.get(0).getClass();
            DataEntityInputBeanDescriptor inputBeanDescriptor =
                    new DataEntityInputBeanDescriptor(this.getEntity(), inputType);

            // 引用属性录入的检索值要先处理，批量查询出对应的id
            inputBeanDescriptor.tryReferencePropertySearch(inputObjects);

            // 在Property.setValue时，不逐一加载id,最后批量加载
            ReferenceProperty.suspendLoadReferenceObject();

            // 如果inputObjects里有标识属性，那么根据这些属性批量加载数据对象
            Map<Object/* identity */, Object/* dataObject */> loadedDataObjects = null;

            boolean hasIdentityPropertyInput = inputBeanDescriptor.hasIdentityPropertyInput(inputType);
            if (hasIdentityPropertyInput) {
                loadedDataObjects = this
                        .tryLoadDataObjectsWhileDoBatchInput(inputBeanDescriptor, inputObjects);
            }

            boolean hasError = false;

            for (Object inputObject : inputObjects) {
                Object identityValue = null;
                Object editObject = null;

                if (hasIdentityPropertyInput) {
                    identityValue = inputBeanDescriptor.getIdentityValue(inputObject);

                    if (identityValue != null) {
                        editObject = loadedDataObjects.get(identityValue);
                    }
                }

                if (editObject != null) {
                    if (option.supportUpdateMode()) {
                        this.setDataObject(editObject);
                    } else {
                        this.throwNotSupportInputModeException(option);
                    }
                } else {
                    if (option.supportInsertMode()) {
                        editObject = this.createDataObject();
                    } else if (option.supportUpdateMode() && hasIdentityPropertyInput && identityValue != null) {
                        this.throwStaleObjectException(identityValue);
                    } else {
                        this.throwNotSupportInputModeException(option);
                    }
                }

                // 逐行录入，录入的代码由inputBeanDescriptor负责给出对应的id值
                boolean noError = this.input0(inputObject, editObject, inputBeanDescriptor, option);
                if (noError) {
                    dataObjects.add(editObject);
                } else {
                    hasError = true;
                    dataObjects.add(null);
                }
            }

            // 处理错误，默认会抛出异常
            option.handleInvalidPropertyValueException();

            if (hasError) {
                retObjects = dataObjects.stream().filter(Objects::nonNull).collect(Collectors.toList());
            }

            // 这里批量加载引用字段的id
            IDataRepository dataRepository = this.getDataRepository();
            dataRepository.fillReferenceFields(this.getEntity().getDataObjectMeta(), retObjects.toArray(new Object[0]));

            // 完成输入后调用
            this.afterInput0(inputObjects, option, dataObjects);

        } finally {
            ReferenceProperty.resumeLoadReferenceObject();

            // 还原之前的数据对象
            this.setDataObject(previousDataObject);
        }

        return retObjects;
    }

    @Override
    public <T> List<T> batchOutput(List<Object> dataObjects, Class<T> outputType) {
        Assert.notEmpty(dataObjects, "parameter 'dataObjects' is required");

        return dataObjects.stream().map(o -> this.output(o, outputType)).collect(Collectors.toList());
    }

    @Override
    public void clearChildObjectList(String childEntityName) {
        ChildEntity childEntity = this.entity.getChildEntity(childEntityName);
        this.assertDataObjectNotNull();

        this.clearChildObjectList(childEntity, this.dataObject);
    }

    @Override
    public Object createChildObject(final String childEntityName) {
        ChildEntity childEntity = this.entity.getChildEntity(childEntityName);
        return this.createChildObject(childEntity);
    }

    @Override
    public Object createDataObject() {
        // 创建数据对象
        this.dataObject = this.internalCreateDataObject(this.entity);
        // 发布数据对象创建事件
        this.eventPublisher.publishDataObjectCreateEvent();

        return dataObject;
    }

    @Override
    public List<Object> getChildObjectList(final String childEntityName) {
        ChildEntity childEntity = this.entity.getChildEntity(childEntityName);
        this.assertDataObjectNotNull();

        return this.getChildObjectList(childEntity, this.dataObject);
    }

    @Override
    public Object getDataObject() {
        return this.dataObject;
    }

    @Override
    public void setDataObject(Object dataObject) {
        this.dataObject = dataObject;
    }

    @Override
    public Entity getEntity() {
        return entity;
    }

    public void setEntity(Entity entity) {
        Assert.notNull(entity, "parameter 'entity' is required");

        this.entity = entity;
        this.entity.getDataObjectMeta();
    }

    @Override
    public <TOutValue extends IValueObject> TOutValue getPropertyValue(Property<TOutValue> property) {
        this.assertDataObjectNotNull();
        return this.getPropertyValue(property, this.dataObject);
    }

    @Override
    public <TOutValue extends IValueObject> TOutValue getPropertyValue(Property<TOutValue> property,
                                                                       Object targetObject) {
        return this.getPropertyValue0(property, targetObject);
    }

    @Override
    public IValueObject getPropertyValue(String propertyAlias) {
        Property<?> property = this.getProperty(propertyAlias);
        return this.getPropertyValue(property);
    }

    @Override
    public Object input(Object inputObject) {
        InputOption option = new InputOption();
        return this.input(inputObject, option);
    }

    @Override
    public Object input(Object inputObject, InputOption option) {
        Assert.notNull(inputObject, "parameter 'inputObject' is required");
        Assert.notNull(option, "parameter 'option' is required");

        // 先记录之前的数据对象
        Object previousDataObject = this.getDataObject();

        try {
            // 开始导入
            Class<?> inputType = inputObject.getClass();
            DataEntityInputBeanDescriptor inputBeanDescriptor = new DataEntityInputBeanDescriptor(this.getEntity(),
                    inputType);

            // 引用属性录入的搜索值，批量查询出对应的id
            inputBeanDescriptor.tryReferencePropertySearch(Collections.singletonList(inputObject));

            Map<Object/* identity */, Object/* dataObject */> loadedDataObjects = null;

            boolean hasIdentityPropertyInput = inputBeanDescriptor.hasIdentityPropertyInput(inputType);
            if (hasIdentityPropertyInput) {
                loadedDataObjects = this
                        .tryLoadDataObjectsWhileDoBatchInput(inputBeanDescriptor, Collections.singletonList(inputObject));
            }

            Object identityValue = null;
            Object dataObject = null;

            if (hasIdentityPropertyInput) {
                identityValue = inputBeanDescriptor.getIdentityValue(inputObject);

                if (identityValue != null) {
                    dataObject = loadedDataObjects.get(identityValue);
                }
            }

            if (dataObject != null) {
                if (option.supportUpdateMode()) {
                    this.setDataObject(dataObject);
                } else {
                    this.throwNotSupportInputModeException(option);
                }
            } else {
                if (option.supportInsertMode()) {
                    if (this.dataObject == null) {
                        dataObject = this.createDataObject();
                    }
                } else if (option.supportUpdateMode() && hasIdentityPropertyInput && identityValue != null) {
                    this.throwStaleObjectException(identityValue);
                } else {
                    this.throwNotSupportInputModeException(option);
                }

                dataObject = this.getDataObject();
            }

            // 逐行录入，录入的代码由inputBeanDescriptor负责给出对应的id值
            boolean noError = this.input0(inputObject, this.dataObject, inputBeanDescriptor, option);
            // 处理错误，默认会抛出异常
            option.handleInvalidPropertyValueException();

            // 完成输入后调用
            List<Object> inputObjects = Collections.singletonList(inputObject);
            List<Object> dataObjects = noError ? Collections.singletonList(dataObject)
                    : Collections.singletonList(null);
            this.afterInput0(inputObjects, option, dataObjects);

            return dataObject;
        } finally {
            this.setDataObject(previousDataObject);
        }
    }

    @Override
    public List<Object> inputChildren(final String childEntityName, final List<?> inputObjects)
            throws InvalidPropertyValueException {
        return this.inputChildren(childEntityName, inputObjects, new InputOption());
    }

    @Override
    public List<Object> inputChildren(final String childEntityName,
                                      final List<?> inputObjects,
                                      final InputOption option) throws InvalidPropertyValueException {
        Assert.notNull(option, "parameter 'option' is required");

        if (CollectionUtils.isEmpty(inputObjects)) {
            return this.getChildObjectList(childEntityName);
        }

        Class<?> inputType = inputObjects.get(0).getClass();
        DataEntityInputBeanDescriptor inputBeanDescriptor = new DataEntityInputBeanDescriptor(
                this.getEntity(), childEntityName, inputType);

        return this.inputChildren(inputBeanDescriptor, childEntityName, inputObjects, option);
    }

    @Override
    public <T> T output(Class<T> outputType) {
        this.assertDataObjectNotNull();
        return this.output(this.getDataObject(), outputType);
    }

    @Override
    public <T> T output(Object dataObject, Class<T> outputType) {
        return this.output(this.entity, dataObject, outputType);
    }

    @Override
    public Map<String, Object> outputAsMap() {
        this.assertDataObjectNotNull();
        return this.outputAsMap(this.getDataObject());
    }

    @Override
    public Map<String, Object> outputAsMap(final Object dataObject) {
        return this.outputAsMap(this.entity, dataObject);
    }

    @Override
    public Map<String, Object> outputAsMap(final Entity entity, final Object dataObject) {
        return this.outputAsMap((AbstractEntity) entity, dataObject);
    }

    @Override
    public void removeChildObjectList(String childEntityName, List<Object> childObjectList) {
        ChildEntity childEntity = this.entity.getChildEntity(childEntityName);
        this.assertDataObjectNotNull();

        this.removeChildObjectList(childEntity, this.dataObject, childObjectList);
    }

    @Override
    public void setPropertyValue(Property<?> property, Object inputValue) {
        this.assertDataObjectNotNull();
        this.setPropertyValue(property, this.dataObject, inputValue);
    }

    @Override
    public void setPropertyValue(Property<?> property, Object targetObject, Object inputValue) {
        // TODO EVENT
        this.setPropertyValue0(property, targetObject, inputValue);
    }

    @Override
    public void setPropertyValue(String propertyAlias, Object inputValue) {
        Property<?> property = this.getProperty(propertyAlias);
        this.setPropertyValue(property, inputValue);
    }

    /**
     * 完成输入后调用，inputObjects和dataObjects的索引是一致的，错误的输入在dataObjects对应null
     *
     * @param dataObjects
     */
    protected void afterInput(List<?> inputObjects, InputOption option, List<Object> dataObjects) {

    }

    protected final Property<?> getProperty(String propertyAlias) {
        return this.entity.getPropertyByAlias(propertyAlias, true);
    }

    protected final <TProperty> TProperty getProperty(Class<TProperty> propertyType, String propertyAlias) {
        Property<?> property = this.getProperty(propertyAlias);

        this.checkPropertyType(property, propertyType);

        @SuppressWarnings("unchecked")
        TProperty expectProperty = (TProperty) property;
        return expectProperty;
    }

    protected Object internalCreateDataObject(AbstractEntity entity) {
        IDataObjectMeta dataObjectMeta = entity.getDataObjectMeta();
        return dataObjectMeta.createDataObject();
    }

    /**
     * 试图加载的id不存在时，抛出此异常
     *
     * @param pkValue
     */
    protected void throwStaleObjectException(Object pkValue) {
        String message = StringResources.get("pisces-domain.AbstractDataEntity.throwStaleObjectException-001", pkValue,
                this.getEntity().toString());
        throw new StaleObjectException(message);
    }

    private void addChildObjectList(ChildEntity childEntity, Object targetObject, List<Object> childObjectList) {
        Assert.notNull(childEntity, "parameter 'childEntity' is required");
        Assert.notNull(targetObject, "parameter 'targetObject' is required");

        if (CollectionUtils.isEmpty(childObjectList)) {
            return;
        }

        this.getChildObjectList(childEntity, targetObject).addAll(childObjectList);
        // TODO EVENT
    }

    private void afterInput0(List<?> inputObjects, InputOption option, List<Object> dataObjects) {
        this.afterInput(inputObjects, option, dataObjects);
    }

    private void assertDataObjectNotNull() {
        Assert.notNull(this.dataObject,
                "'dataObject' is required,createDataObject() or setDataObject(Object) must be invoked.");
    }

    private void checkPropertyType(Property<?> property, Class<?> expectType) {
        if (expectType.isAssignableFrom(property.getClass())) {
            return;
        }

        throw new IllegalArgumentException(expectType.getSimpleName() + " is expected,but " + property.getAlias()
                + " is " + property.getClass().getSimpleName());
    }

    private void clearChildObjectList(ChildEntity childEntity, Object targetObject) {
        Assert.notNull(childEntity, "parameter 'childEntity' is required");
        Assert.notNull(targetObject, "parameter 'targetObject' is required");

        this.getChildObjectList(childEntity, targetObject).clear();
        // TODO EVENT
    }

    private Object createChildObject(final ChildEntity childEntity) {
        Object childObject = this.internalCreateDataObject(childEntity);
        //TODO EVENT
        return childObject;
    }

    private <T> void fillOuptputValue(BeanFieldDescriptor beanFieldDescriptor,
                                      AbstractEntity entity,
                                      Object dataObject,
                                      T output) {
        Object value = null;

        PropertyOutput propertyOutput = beanFieldDescriptor.getAnnotation(PropertyOutput.class);
        ListOutput listOutput = beanFieldDescriptor.getAnnotation(ListOutput.class);

        if (propertyOutput != null) {
            String propertyAlias = this.getPropertyAlias(beanFieldDescriptor, propertyOutput);
            Property<?> property = entity.getProperty(propertyAlias);

            if (property == null) {
                throw new IllegalArgumentException("PropertyOutput:'" + propertyAlias + "' not found!");
            }

            value = this.getPropertyOutValue(beanFieldDescriptor, propertyOutput, property, dataObject);
        } else if (listOutput != null) {
            ChildEntity childEntity = entity.getChildEntity(listOutput.childEntity());

            if (childEntity == null) {
                throw new IllegalArgumentException("ListOutput:'" + listOutput.childEntity() + "' not found!");
            }

            Class<?> itemType = beanFieldDescriptor.getCollectionItemType();
            List<Object> childObjectList = this.getChildObjectList(childEntity, dataObject);
            value = ListsUtils.map(childObjectList, childObject -> this.output(childEntity, childObject, itemType));
        }

        if (value != null) {
            beanFieldDescriptor.setValue(output, value, true);
        }

    }

    private List<Object> getChildObjectList(ChildEntity childEntity, Object targetObject) {
        Assert.notNull(childEntity, "parameter 'childEntity' is required");
        Assert.notNull(targetObject, "parameter 'targetObject' is required");

        return childEntity.getValue(targetObject);
    }

//    private Object fetchListValue(final ChildEntity childEntity,
//                                  final BeanFieldDescriptor beanFieldDescriptor,
//                                  final PropertyOutput propertyOutput) {
//        List<Object> childObjectList = this.getChildObjectList(childEntity, dataObject);
//    }

//    private Object fetchPropertyValue(Property<?> property,
//                                      BeanFieldDescriptor beanFieldDescriptor,
//                                      PropertyOutput propertyOutput) {
//        if (property.getHostEntity().equals(this.entity)) {
//            return this.getPropertyOutValue(beanFieldDescriptor, propertyOutput, property, dataObject);
//        } else {
//            ChildEntity childEntity = (ChildEntity) property.getHostEntity();
//            List<Object> childObjectList = this.getChildObjectList(childEntity, dataObject);
//
//            if (beanFieldDescriptor.isCollectionType()) {
//                // 集合输出，将属性值迭代映射到集合的项类型上
//                return childObjectList.stream().map(childObject -> this.getPropertyOutValue(beanFieldDescriptor,
//                        propertyOutput, property, childObject)).filter(o -> o != null).collect(Collectors.toList());
//            } else {
//                // TODO 非集合输出，聚合子实体属性
//                return null;
//            }
//        }
//    }

    private String getPropertyAlias(BeanFieldDescriptor beanFieldDescriptor, PropertyOutput propertyOutput) {
        String propertyAlias = propertyOutput.value();
        if (StringUtils.isEmpty(propertyAlias)) {
            propertyAlias = propertyOutput.propertyAlias();
        }

        if (StringUtils.isEmpty(propertyAlias)) {
            propertyAlias = beanFieldDescriptor.getName();
        }

        return propertyAlias;
    }

    /**
     * 从dataObject获取指定属性的值，并按照propertyOutput的valueType进行输出
     *
     * @param beanFieldDescriptor
     * @param propertyOutput
     * @param property
     * @param dataObject
     * @return
     */
    private Object getPropertyOutValue(BeanFieldDescriptor beanFieldDescriptor, PropertyOutput propertyOutput,
                                       Property<?> property, Object dataObject) {
        IValueObject propertyValue = property.getValue(dataObject);

        if (propertyOutput.valueType().equals(ValueType.PresentValue)) {
            return propertyValue.getPresentValue();
        } else if (propertyOutput.valueType().equals(ValueType.ObjectValue)) {
            if (property instanceof ReferenceProperty) {
                Class<?> objectValueType = null;

                if (beanFieldDescriptor.isCollectionType()) {
                    objectValueType = beanFieldDescriptor.getCollectionItemType();
                } else {
                    objectValueType = beanFieldDescriptor.getProperyType();
                }

                Entity referenceEntity = ((ReferenceProperty) property).getReferenceEntity();
                Object referenceObject = ((ReferenceObjectValue) propertyValue).getReferenceObject();

                if (referenceObject != null) {
                    return referenceEntity.createDataEntity().output(referenceObject, objectValueType);
                } else {
                    return null;
                }
            } else {
                throw new IllegalStateException("propertyOutput.valueType() is ObjectValue, property:'"
                        + property.toString() + "' should be an ReferenceProperty");
            }
        } else {
            return propertyValue.getDataValue();
        }
    }

    private <TOutValue extends IValueObject> TOutValue getPropertyValue0(Property<TOutValue> property,
                                                                         Object targetObject) {
        return property.getValue(targetObject);
    }

    /**
     * 将对象填充到dataObject中,如果有错误返回false
     *
     * @param inputObject
     * @param option
     */
    private boolean input0(Object inputObject,
                           Object dataObject,
                           DataEntityInputBeanDescriptor inputBeanDescriptor,
                           InputOption option) {
        boolean ret = true;

        Class<?> inputBeanType = inputObject.getClass();
        Collection<PropertyInputDescriptor> propertyInputDescriptors =
                inputBeanDescriptor.getPropertyInputDescriptors(inputBeanType);

        for (PropertyInputDescriptor propertyInputDescriptor : propertyInputDescriptors) {
//            if (PropertyInputDescriptor.isList(propertyInputDescriptor)) {
//                continue;
//            }

            Property<?> property = propertyInputDescriptor.property;
            Object inputValue = propertyInputDescriptor.getValue(inputObject);

            if (inputValue == null && propertyInputDescriptor.ignoreNull) {
                continue;
            }

            if (PropertyInputDescriptor.isList(propertyInputDescriptor)) {
                DataEntityInputBeanDescriptor.ListPropertyInputDescriptor listPropertyInputDescriptor =
                        (DataEntityInputBeanDescriptor.ListPropertyInputDescriptor) propertyInputDescriptor;
                List<Object> listValues = (List<Object>) inputValue;

                this.inputChildren(inputBeanDescriptor, listPropertyInputDescriptor.childEntityName,
                        listValues, option);
            } else {
                try {
                    this.setPropertyValue(property, dataObject, inputValue);
                } catch (InvalidPropertyValueException e) {
                    if (propertyInputDescriptor.ignoreInvalid) {
                        continue;
                    }

                    option.mergeInvalidPropertyValueException(e, inputObject);
                    ret = false;
                }
            }
        }

        return ret;
    }

    private List<Object> inputChildren(
            final DataEntityInputBeanDescriptor inputBeanDescriptor,
            final String childEntityName,
            final List<?> inputObjects,
            final InputOption option) throws InvalidPropertyValueException {
        Assert.notNull(option, "parameter 'option' is required");

        if (CollectionUtils.isEmpty(inputObjects)) {
            return this.getChildObjectList(childEntityName);
        }

        Class<?> inputType = inputObjects.get(0).getClass();

        try {
            // 引用属性录入的检索值要先处理，批量查询出对应的id
            inputBeanDescriptor.tryReferencePropertySearch(inputObjects);

            // 在Property.setValue时，不逐一加载id,最后批量加载
            ReferenceProperty.suspendLoadReferenceObject();

            boolean hasIdentityPropertyInput = inputBeanDescriptor.hasIdentityPropertyInput(inputType);
            boolean hasError = false;

            ChildEntity childEntity = this.entity.getChildEntity(childEntityName);
            List<Object> childObjectList = this.getChildObjectList(childEntity, this.dataObject);

            Map<Object/* identity */, Object/* dataObject */> loadedDataObjects = null;

            if (option.isClearChildren(childEntityName)) {
                childObjectList.clear();
                loadedDataObjects = Collections.emptyMap();
            } else {
                loadedDataObjects = childObjectList.stream().collect(Collectors.toMap(childEntity::getIdValue, o -> o));
            }

            for (Object inputObject : inputObjects) {
                Object identityValue = null;
                Object editObject = null;

                if (hasIdentityPropertyInput) {
                    identityValue = inputBeanDescriptor.getIdentityValue(inputObject);

                    if (identityValue != null) {
                        editObject = loadedDataObjects.get(identityValue);
                    }
                }

                if (editObject != null) {
                    if (!option.supportUpdateMode()) {
                        this.throwNotSupportInputModeException(option);
                    }
                } else {
                    if (option.supportInsertMode()) {
                        editObject = this.createChildObject(childEntity);
                    } else if (option.supportUpdateMode() && hasIdentityPropertyInput && identityValue != null) {
                        this.throwStaleObjectException(identityValue);
                    } else {
                        this.throwNotSupportInputModeException(option);
                    }
                }

                // 逐行录入，录入的代码由inputBeanDescriptor负责给出对应的id值
                boolean noError = this.input0(inputObject, editObject, inputBeanDescriptor, option);
                if (noError) {
                    if (childEntity.isNew(editObject)) {
                        childObjectList.add(editObject);
                    }
                } else {
                    hasError = true;
                }
            }

            // 处理错误，默认会抛出异常
            option.handleInvalidPropertyValueException();

            // 这里批量加载引用字段的id
            IDataRepository dataRepository = this.getDataRepository();
            dataRepository.fillReferenceFields(childEntity.getDataObjectMeta(), childObjectList.toArray(new Object[0]));

            // 完成输入后调用
//            this.afterInputChildren(inputObjects, option, childObjectList);

            return childObjectList;
        } finally {
            ReferenceProperty.resumeLoadReferenceObject();
        }
    }

    private <T> T output(AbstractEntity entity, Object dataObject, Class<T> outputType) {
        Assert.notNull(entity, "parameter 'entity' is required");
        Assert.notNull(dataObject, "parameter 'dataObject' is required");
        Assert.notNull(outputType, "parameter 'outputType' is required");

        T output = ReflectionUtils.createNewInstance(outputType);

        BeanFieldDescriptor[] beanFieldDescriptors = BeanFieldDescriptor.getBeanFieldDescriptors(outputType);
        for (BeanFieldDescriptor beanFieldDescriptor : beanFieldDescriptors) {
            this.fillOuptputValue(beanFieldDescriptor, entity, dataObject, output);
        }

        return output;
    }

    private Map<String, Object> outputAsMap(AbstractEntity entity, Object dataObject) {
        Assert.notNull(entity, "parameter 'entity' is required");
        Assert.notNull(dataObject, "parameter 'dataObject' is required");

        Map<String, Object> mapObject =
                Maps.newLinkedHashMapWithExpectedSize(entity.getDataObjectMeta().getFields().size());

        entity.fillMapObject(this,dataObject,mapObject);

        for (Property<?> property : entity.getProperties()) {
            property.fillMapObject(this, dataObject, mapObject);
        }

        for (ChildEntity childEntity : entity.getChildEntities()) {
            List<Object> childObjectList = this.getChildObjectList(childEntity, dataObject);
            mapObject.put(
                    childEntity.getName(),
                    ListsUtils.map(childObjectList, childObject -> this.outputAsMap(childEntity, childObject))
            );
        }

        return mapObject;
    }

    private void removeChildObjectList(ChildEntity childEntity, Object targetObject, List<Object> childObjectList) {
        Assert.notNull(childEntity, "parameter 'childEntity' is required");
        Assert.notNull(targetObject, "parameter 'targetObject' is required");

        if (CollectionUtils.isEmpty(childObjectList)) {
            return;
        }

        this.getChildObjectList(childEntity, targetObject).removeAll(childObjectList);
        // TODO EVENT
    }

    private void setPropertyValue0(Property<?> property, Object targetObject, Object inputValue) {
        property.setValue(targetObject, inputValue);
    }

    private void throwNotSupportInputModeException(InputOption option) {
        if (!option.supportInsertMode()) {
            throw new IllegalStateException("option is not support insert mode!");
        }

        if (!option.supportUpdateMode()) {
            throw new IllegalStateException("option is not support update mode!");
        }
    }

    /**
     * 执行批量导入时，如果输入对象里含有标识值，那么需要从数据仓库加载数据对象，inputObject需要填充到这个对象中。因为是批量操作，所以通过map返回找到的数据对象
     *
     * @param inputBeanDescriptor
     * @param inputObjects
     * @return
     */
    private Map<Object/* identity */, Object/* dataObject */> tryLoadDataObjectsWhileDoBatchInput(
            DataEntityInputBeanDescriptor inputBeanDescriptor, List<?> inputObjects) {
        Object[] ids = inputObjects.stream().map(inputObject -> inputBeanDescriptor.getIdentityValue(inputObject))
                .filter(Objects::nonNull).toArray(l -> new Object[l]);

        if (ids.length == 0) {
            return Collections.emptyMap();
        }

        IDataObjectMeta dataObjectMeta = this.getEntity().getDataObjectMeta();
        Object[] objects = this.getDataRepository().loadBatchForUpdate(dataObjectMeta, ids);

        return Stream.of(objects).collect(Collectors.toMap(o -> dataObjectMeta.getPkField().getValue(o), o -> o));
    }

}
